import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.image.*;
import java.util.List;
import java.util.LinkedList;
import java.util.ArrayList;

class Dot
{
    public Interpolator interpolator;
    public float x;
    public float y;
    public Color color;
    
    public Dot (Interpolator interpolator, Color color)
    {
        this.interpolator = interpolator;
        this.color = color;
    }
}

public class SteveComponent extends JComponent
{
    public Staff staff;
    public Sidebar sidebar;
    
    public void init (Dimension s)
    {
        this.setLayout(new BorderLayout());
        
        int width = s.width;
        int height = s.height;
        
        Dot[] dots_arr = new Dot[]{
            new Dot(new LinearInterpolator(), Color.GREEN),
            new Dot(new CubicInterpolator(), Color.RED),
            new Dot(new SinusoidalInterpolator(), Color.MAGENTA),
            new Dot(new QuarterCircleInterpolator(), Color.BLUE),
            new Dot(new QuadraticInterpolator(), Color.YELLOW)
        };
        List<Dot> dots = new ArrayList<Dot>(dots_arr.length);
        for (int k=0; k<dots_arr.length; k++)
        {
            Dot dot = dots_arr[k];
            dot.y = (float)(k + 1) / (float)(dots_arr.length + 1) * height;
            dots.add(dot);
        }
        
        int N = dots.size();
        int b = 16;
        int dbig = height / (N + 1);
        int d = dbig - b * 2;
        
        staff = new Staff();
        staff.init(dots, new Dimension(width - dbig, height));
        staff.setPreferredSize(new Dimension(width - dbig, -1));
        this.add(staff, BorderLayout.CENTER);
        
        sidebar = new Sidebar();
        sidebar.init(dots, d, b);
        sidebar.setPreferredSize(new Dimension(dbig, -1));
        this.add(sidebar, BorderLayout.EAST);
    }
    public void stop ()
    {
        staff.stop();
    }
}

class Staff extends JComponent implements Runnable
{
    public float low;
    public float hi;
    public boolean is_running = false;
    public BufferedImage buffer;
    public Graphics2D offscreen;
    List<Dot> dots;
    
    public void init (List<Dot> dots, Dimension s)
    {
        this.dots = dots;
        
        int width = s.width;
        int height = s.height;
        low = 50.0f;
        hi = (float)width - low;
        
        buffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        offscreen = buffer.createGraphics();
        offscreen.setBackground(Color.LIGHT_GRAY);
        offscreen.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        
        is_running = true;
        new Thread(this).start();
    }
    
    
    public void stop ()
    {
        is_running = false;
    }
    
    public void run ()
    {
        float ratio;
        float time = 0.0f;
        while (is_running)
        {
            time += 1.0 / 60.0f;
            while (time > 2.0f) { time -= 2.0f; }
            if (time < 1.0f)
            {
                ratio = time;
            }
            else
            {
                ratio = 1.0f - (time - 1.0f);
            }
            for (Dot dot : dots)
            {
                dot.x = dot.interpolator.interpolate(ratio, low, hi);
            }
            this.repaint();
            try { Thread.sleep(16); } catch (InterruptedException ex) { }
        }
    }
    
    public void paint (Graphics g)
    {
        really_paint(offscreen);
        g.drawImage(buffer, 0, 0, null);
    }
    public void really_paint (Graphics2D g)
    {
        g.clearRect(0, 0, buffer.getWidth(), buffer.getHeight());
        
        float r = 8.0f;
        for (Dot dot : dots)
        {
            g.setColor(Color.BLACK);
            g.drawLine((int)low, (int)dot.y, (int)hi, (int)dot.y);
            g.setColor(dot.color);
            g.fillOval((int)(dot.x - r), (int)(dot.y - r), (int)r * 2, (int)r * 2);
        }
    }
}

class Sidebar extends JPanel
{
    public void init (List<Dot> dots, int d, int b)
    {
        this.setLayout(null);
        this.setBackground(Color.LIGHT_GRAY);
        
        int N = dots.size();
        //BufferedImage[] graphs = new BufferedImage[N];
        for (int k=0; k<N; k++)
        {
            Dot dot = dots.get(k);
            int w = d;
            int h = d;
            BufferedImage graph = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g = graph.createGraphics();
            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g.setBackground(Color.WHITE);
            g.clearRect(0, 0, w, h);
            g.setColor(Color.RED);
            {
                int n = d;
                int[] xp = new int[n];
                int[] yp = new int[n];
                for (int i=0; i<n; i++)
                {
                    float xx = (float)i / (float)n;
                    float yy = 1.0f - dot.interpolator.interpolate(xx, 0.0f, 1.0f);
                    xp[i] = (int)Math.round(xx * n);
                    yp[i] = (int)Math.round(yy * n);
                }
                g.drawPolyline(xp, yp, n);
            }
            int dbig = d + b*2;
            int x = b;
            int y = (k + 1) * dbig - d/2;
            
            JLabel graph_label = new JLabel(new ImageIcon(graph));
            graph_label.setLocation(x, y);
            graph_label.setSize(w, h);
            
            JLabel name_label = new JLabel(dot.interpolator.getName());
            name_label.setFont(new Font("Helvetica", Font.BOLD, 12));
            w = name_label.getPreferredSize().width;
            h = name_label.getPreferredSize().height;
            x = graph_label.getX() + graph_label.getWidth()/2 - w/2;
            y = graph_label.getY() - h - 2;
            name_label.setLocation(x, y);
            name_label.setSize(w, h);
            
            this.add(graph_label);
            this.add(name_label);
        }
    }
}
